/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.maven.nodetype.writer;

import offset.nodes.maven.nodetype.vo.PropertyDefVO;
import offset.nodes.maven.nodetype.vo.ChildDefVO;
import offset.nodes.maven.nodetype.vo.NodeTypeVO;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import offset.nodes.maven.nodetype.JavaPrinter;
import offset.nodes.maven.nodetype.Name;
import offset.nodes.maven.nodetype.NodeTypeMojo;

/**
 *
 * @author Walter L�tgenau
 */
public class InterfaceWriter {

    NodeTypeVO nodeType;
    JavaPrinter printer;
    HashMap<String, NodeTypeVO> types;
    public static final String HEADER = "/* This class has been generated. Do not modify it directly */";
    public static String[] CONSTANT_IMPORTS = new String[]{
        "import javax.jcr.RepositoryException;",
        "import javax.jcr.Node;",
        "import javax.jcr.Value;",
        "import javax.jcr.NodeIterator;",
        "import java.util.Calendar;"
    };
    protected HashSet<String> nodeTypesDone = new HashSet<String>();

    public InterfaceWriter(NodeTypeVO nodeType, JavaPrinter printer, HashMap<String, NodeTypeVO> types) {
        this.nodeType = nodeType;
        this.printer = printer;
        this.types = types;
    }

    public void write() throws IOException {
        // add generation comment
        printer.printLine(HEADER);
        printer.appendLine();

        // add package from namespace
        Name name = new Name(nodeType.getName());

        String packageDeclaration = "package " + NodeTypeMojo.GENERATED_PACKAGE + "." + name.getPrefix() + ";";
        printer.printLine(packageDeclaration);
        printer.appendLine();

        // generate constant imports, e.g. the child node types.
        printer.printLines(CONSTANT_IMPORTS);

        // add header
        printer.appendLine();
        printer.printLine("public interface " + name.nameToJavaClassName() + " {");

        // generate constant methods: constructor etc.
        printer.incrementIndent();
        printer.printLine("public Node getNode();");
        printer.appendLine();

        writeMethods(nodeType);

        // add trailer
        printer.decrementIndent();
        printer.printLine("}");

        printer.close();
    }

    public void writeMethods(NodeTypeVO type) throws IOException {
        if (nodeTypesDone.contains(type.getName()))
            return;

        String interfaceName = null;
       if (type != nodeType) {
            Name typeName = new Name(type.getName());
            interfaceName = NodeTypeMojo.GENERATED_PACKAGE + Name.PACKAGE_DELIMITER + typeName.getPrefix() + Name.PACKAGE_DELIMITER + typeName.nameToJavaClassName();
        }

        // write properties
        for (PropertyDefVO property : type.getProperties()) {
            writeProperty(property, interfaceName);
        }

        // write children
        for (ChildDefVO child : type.getChildren()) {
            writeChild(child, interfaceName);
        }

        nodeTypesDone.add(type.getName());

        for (String superType : type.getSuperTypes()) {
            writeMethods(types.get(superType));
        }
    }

    public void writeProperty(PropertyDefVO property, String interfaceName) throws IOException {
        if (property.getName() == null)
            return;

        Name name = new Name(property.getName());

        // generate property constant only for properties not inherited by interfaces (= superTypes)
        if (interfaceName == null) {
            printer.printLine("public static final String " + name.nameToConstant() + " = \"" + property.getName() + "\";");
        }

        // generate has... method
        printer.printLine("public boolean has" + name.nameToJavaClassName() + "() throws RepositoryException;");

        // generate get... method
        printer.printLine("public " + name.propertyTypeToJava(property.getType()) + (property.isMultiple() ? "[]" : "") +" get" + name.nameToJavaClassName() + "() throws RepositoryException;");

        // generate set... method
        printer.printLine("public void set" + name.nameToJavaClassName() + "(" 
                + name.propertyTypeToJava(property.getType())
                + (property.isMultiple() ? "[]" : "")
                + " " + name.nameToJavaVariableName() + ") throws RepositoryException;");
        printer.appendLine();
    }

    public void writeChild(ChildDefVO child, String interfaceName) throws IOException {
        if (child.getName() == null)
            return;

        Name name = new Name(child.getName());
        String constantName = null;
        if (interfaceName != null)
            constantName = interfaceName + "." + name.nameToConstant();
        else
            constantName = name.nameToConstant();

        Name type = new Name(child.getPrimaryType());

        // generate child node constant
        if (interfaceName == null) {
            printer.printLine("public static final String " + constantName + " = \"" + child.getName() + "\";");
        }

        // generate has... method
        printer.printLine("public boolean has" + name.nameToJavaClassName() + "() throws RepositoryException;");

        // generate get... method
        printer.printLine("public " + type.nameToJavaPackageAndClassName() + " get" + name.nameToJavaClassName() + "() throws RepositoryException;");

        // generate add... or set... method
        printer.printLine("public void set" + name.nameToJavaClassName() + "(" + type.nameToJavaPackageAndClassName() + " " + name.nameToJavaVariableName() + ") throws RepositoryException;");
        printer.appendLine();
    }
}
